package com.zy.explore.client.infrastructure.util;

/**
 * 类说明：雪花算法 - 用于生成唯一主键
 * 版本：1.0.0
 *
 * @author 🇨🇳中国印🇨🇳
 * @date 2022年04月17日 02:38:46
 * @since 1.0.0
 */
public class Snowflake {

    private static final long workerIDBits = 5L;

    private static final long datacenterIDBits = 5L;

    private static long workerID;

    private static long datacenterID;

    private static long sequence = 0L;

    private static long lastTimestamp = -1L;

    private Snowflake() {
    }

    public Snowflake(long workerID, long datacenterID) {
        long maxWorkerID = ~(-1L << workerIDBits);
        if (workerID > maxWorkerID || workerID < 0) {
            throw new IllegalArgumentException(String.format("worker ID can't be greater than %d or less than 0", maxWorkerID));
        }
        long maxDatacenterID = ~(-1L << datacenterIDBits);
        if (datacenterID > maxDatacenterID || datacenterID < 0) {
            throw new IllegalArgumentException(String.format("datacenter ID can't be greater than %d or less than 0", maxDatacenterID));
        }
        Snowflake.workerID = workerID;
        Snowflake.datacenterID = datacenterID;
    }

    public static synchronized String pkID() {
        return String.valueOf(nextID());
    }

    public static synchronized long nextID() {
        long timestamp = timeGen();

        if (timestamp < lastTimestamp) {
            throw new RuntimeException(String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
        }

        long sequenceBits = 12L;
        if (lastTimestamp == timestamp) {
            long sequenceMask = ~(-1L << sequenceBits);
            sequence = (sequence + 1) & sequenceMask;
            if (sequence == 0) {
                timestamp = tilNextMillis(lastTimestamp);
            }
        } else {
            sequence = 0L;
        }

        lastTimestamp = timestamp;

        long datacenterIDShift = sequenceBits + workerIDBits;
        long timestampLeftShift = sequenceBits + workerIDBits + datacenterIDBits;
        long twepoch = 1420041600000L;
        
        return ((timestamp - twepoch) << timestampLeftShift)
                | (datacenterID << datacenterIDShift)
                | (workerID << sequenceBits)
                | sequence;
    }

    protected static long tilNextMillis(long lastTimestamp) {
        long timestamp = timeGen();
        while (timestamp <= lastTimestamp) {
            timestamp = timeGen();
        }
        return timestamp;
    }

    protected static long timeGen() {
        return System.currentTimeMillis();
    }

}
